﻿#include "MLibraryV2.h"
#include "../../Shared/Enums.h"

namespace LibraryEditor
{

bool MLibraryV2::Load = true;

    MLibraryV2::MLibraryV2(const std::string &filename)
    {
        FileName = filename;
        Initialize();
        Close();
    }

    void MLibraryV2::Initialize()
    {
        int CurrentVersion;
        _initialized = true;

        if (!FileSystem::fileExists(FileName))
        {
            return;
        }

        _stream = new FileStream(FileName, FileMode::Open, FileAccess::ReadWrite);
        _reader = new BinaryReader(_stream);
        CurrentVersion = _reader->ReadInt32();

        if (CurrentVersion < 2)
        {
            MessageBox::Show("Wrong version, expecting lib version: " + std::to_string(LibVersion) + " found version: " + std::to_string(CurrentVersion) + ".", "Failed to open", MessageBoxButtons::OK, MessageBoxIcon::Error, MessageBoxDefaultButton::Button1);
            return;
        }

        Count = _reader->ReadInt32();

        Images = std::vector<MImage*>();
        IndexList = std::vector<int>();

        int frameSeek = 0;
        if (CurrentVersion >= 3)
        {
            frameSeek = _reader->ReadInt32();
        }

        for (int i = 0; i < Count; i++)
        {
            IndexList.push_back(_reader->ReadInt32());
        }

        for (int i = 0; i < Count; i++)
        {
            Images.push_back(nullptr);
        }

        for (int i = 0; i < Count; i++)
        {
            CheckImage(i);
        }

        if (CurrentVersion >= 3)
        {
            _stream->Seek(frameSeek, SeekOrigin::Begin);

            auto frameCount = _reader->ReadInt32();
            for (int i = 0; i < frameCount; i++)
            {
                Frame tempVar(_reader);
                Frames->emplace(static_cast<MirAction>(_reader->ReadByte()), &tempVar);
            }
        }
    }

    void MLibraryV2::Close()
    {
        if (_stream != nullptr)
        {
            _stream->Dispose();
        }
    }

    void MLibraryV2::Save()
    {
        Close();

        MemoryStream *stream = new MemoryStream();
        BinaryWriter *writer = new BinaryWriter(stream);

        Count = Images.size();
        IndexList.clear();

        int offSet = (4 + 4 + 4) + (Count * 4);
        for (int i = 0; i < Count; i++)
        {
            IndexList.push_back(static_cast<int>(stream->Length) + offSet);
            Images[i]->Save(writer);
        }

        auto frameSeek = static_cast<int>(stream->Length) + offSet;

        writer->Flush();
        std::vector<unsigned char> fBytes = stream->ToArray();

        _stream = File::Create(FileName);
        writer = new BinaryWriter(_stream);

        writer->Write(LibVersion);

        writer->Write(Count);

        writer->Write(frameSeek);

        for (int i = 0; i < Count; i++)
        {
            writer->Write(IndexList[i]);
        }

        writer->Write(fBytes);

        writer->Write(Frames->getKeys()->Count);
        for (auto action : *Frames)
        {
            writer->Write(static_cast<unsigned char>(action->first));
            Frames[action->first]->Save(writer);
        }

        writer->Flush();
        writer->Close();
        writer->Dispose();
        Close();

//C# TO C++ CONVERTER TODO TASK: A 'delete writer' statement was not added since writer was passed to a method or constructor. Handle memory management manually.
//C# TO C++ CONVERTER TODO TASK: A 'delete stream' statement was not added since stream was passed to a method or constructor. Handle memory management manually.
    }

    void MLibraryV2::CheckImage(int index)
    {
        if (!_initialized)
        {
            Initialize();
        }

        if (Images.empty() || index < 0 || index >= Images.size())
        {
            return;
        }

        if (Images[index] == nullptr)
        {
            _stream->Position = IndexList[index];
            Images[index] = new MImage(_reader);
        }

        if (!Load)
        {
            return;
        }

        MImage *mi = Images[index];
        if (!mi->TextureValid)
        {
            _stream->Seek(IndexList[index] + 12, SeekOrigin::Begin);
            mi->CreateTexture(_reader);
        }
    }

    Point *MLibraryV2::GetOffSet(int index)
    {
        if (!_initialized)
        {
            Initialize();
        }

        if (Images.empty() || index < 0 || index >= Images.size())
        {
            return Point::Empty;
        }

        if (Images[index] == nullptr)
        {
            _stream->Seek(IndexList[index], SeekOrigin::Begin);
            Images[index] = new MImage(_reader);
        }

        return new Point(Images[index]->X, Images[index]->Y);
    }

    Size *MLibraryV2::GetSize(int index)
    {
        if (!_initialized)
        {
            Initialize();
        }
        if (Images.empty() || index < 0 || index >= Images.size())
        {
            return Size::Empty;
        }

        if (Images[index] == nullptr)
        {
            _stream->Seek(IndexList[index], SeekOrigin::Begin);
            Images[index] = new MImage(_reader);
        }

        return new Size(Images[index]->Width, Images[index]->Height);
    }

    MLibraryV2::MImage *MLibraryV2::GetMImage(int index)
    {
        if (index < 0 || index >= Images.size())
        {
            return nullptr;
        }

        return Images[index];
    }

    Bitmap *MLibraryV2::GetPreview(int index)
    {
        if (index < 0 || index >= Images.size())
        {
            return new Bitmap(1, 1);
        }

        MImage *image = Images[index];

        if (image == nullptr || image->Image == nullptr)
        {
            return new Bitmap(1, 1);
        }

        if (image->Preview == nullptr)
        {
            image->CreatePreview();
        }

        return image->Preview;
    }

    void MLibraryV2::AddImage(Bitmap *image, short x, short y)
    {
        MImage *mImage = new MImage(image);
        mImage->X = x;
        mImage->Y = y;

        Count++;
        Images.push_back(mImage);

//C# TO C++ CONVERTER TODO TASK: A 'delete mImage' statement was not added since mImage was passed to a method or constructor. Handle memory management manually.
    }

    void MLibraryV2::AddImage(Bitmap *image, Bitmap *maskImage, short x, short y)
    {
        MImage *mImage = new MImage(image, maskImage);
        mImage->X = x;
        mImage->Y = y;

        Count++;
        Images.push_back(mImage);

//C# TO C++ CONVERTER TODO TASK: A 'delete mImage' statement was not added since mImage was passed to a method or constructor. Handle memory management manually.
    }

    void MLibraryV2::ReplaceImage(int Index, Bitmap *image, short x, short y)
    {
        MImage *mImage = new MImage(image);
        mImage->X = x;
        mImage->Y = y;

        Images[Index] = mImage;

        delete mImage;
    }

    void MLibraryV2::InsertImage(int index, Bitmap *image, short x, short y)
    {
        MImage *mImage = new MImage(image);
        mImage->X = x;
        mImage->Y = y;

        Count++;
        Images.Insert(index, mImage);

//C# TO C++ CONVERTER TODO TASK: A 'delete mImage' statement was not added since mImage was passed to a method or constructor. Handle memory management manually.
    }

    void MLibraryV2::RemoveImage(int index)
    {
        if (Images.empty() || Count <= 1)
        {
            Count = 0;
            Images = std::vector<MImage*>();
            return;
        }
        Count--;

        Images.erase(Images.begin() + index);
    }

    bool MLibraryV2::CompareBytes(std::vector<unsigned char> &a, std::vector<unsigned char> &b)
    {
        if (a == b)
        {
            return true;
        }

        if (a.empty() || b.empty() || a.size() != b.size())
        {
            return false;
        }

        for (int i = 0; i < a.size(); i++)
        {
            if (a[i] != b[i])
            {
                return false;
            }
        }

        return true;
    }

    void MLibraryV2::RemoveBlanks(bool safe)
    {
        for (int i = Count - 1; i >= 0; i--)
        {
            if (Images[i]->FBytes.empty() || Images[i]->FBytes.size() <= 24)
            {
                if (!safe)
                {
                    RemoveImage(i);
                }
                else if (Images[i]->X == 0 && Images[i]->Y == 0)
                {
                    RemoveImage(i);
                }
            }
        }
    }

    MLibraryV2::MImage::MImage(BinaryReader *reader)
    {
        //read layer 1
        Width = reader->ReadInt16();
        Height = reader->ReadInt16();
        X = reader->ReadInt16();
        Y = reader->ReadInt16();
        ShadowX = reader->ReadInt16();
        ShadowY = reader->ReadInt16();
        Shadow = reader->ReadByte();
        Length = reader->ReadInt32();
        FBytes = reader->ReadBytes(Length);
        //check if there's a second layer and read it
        HasMask = ((Shadow >> 7) == 1) ? true : false;
        if (HasMask)
        {
            MaskWidth = reader->ReadInt16();
            MaskHeight = reader->ReadInt16();
            MaskX = reader->ReadInt16();
            MaskY = reader->ReadInt16();
            MaskLength = reader->ReadInt32();
            MaskFBytes = reader->ReadBytes(MaskLength);
        }
    }

    MLibraryV2::MImage::MImage(std::vector<unsigned char> &image, short Width, short Height)
    {
        FBytes = image;
        this->Width = Width;
        this->Height = Height;
    }

    MLibraryV2::MImage::MImage(Bitmap *image)
    {
        if (image == nullptr)
        {
            FBytes = std::vector<unsigned char>(0);
            return;
        }

        Width = static_cast<short>(image->Width);
        Height = static_cast<short>(image->Height);

        Image = image; // FixImageSize(image);
        FBytes = ConvertBitmapToArray(Image);
    }

    MLibraryV2::MImage::MImage(Bitmap *image, Bitmap *Maskimage)
    {
        if (image == nullptr)
        {
            FBytes = std::vector<unsigned char>(0);
            return;
        }

        Width = static_cast<short>(image->Width);
        Height = static_cast<short>(image->Height);
        Image = image; // FixImageSize(image);
        FBytes = ConvertBitmapToArray(Image);
        if (Maskimage == nullptr)
        {
            MaskFBytes = std::vector<unsigned char>(0);
            return;
        }
        HasMask = true;
        MaskWidth = static_cast<short>(Maskimage->Width);
        MaskHeight = static_cast<short>(Maskimage->Height);
        MaskImage = Maskimage; // FixImageSize(Maskimage);
        MaskFBytes = ConvertBitmapToArray(MaskImage);
    }

    Bitmap *MLibraryV2::MImage::FixImageSize(Bitmap *input)
    {
        int w = input->Width + (4 - input->Width % 4) % 4;
        int h = input->Height + (4 - input->Height % 4) % 4;

        if (input->Width != w || input->Height != h)
        {
            Bitmap *temp = new Bitmap(w, h);
//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (Graphics g = Graphics.FromImage(temp))
            {
                Graphics g = Graphics::FromImage(temp);
                g.Clear(Color::Transparent);
                g.InterpolationMode = InterpolationMode::NearestNeighbor;
                g.DrawImage(input, 0, 0);
                g.Save();
            }
            input->Dispose();
            input = temp;

//C# TO C++ CONVERTER TODO TASK: A 'delete temp' statement was not added since temp was passed to a method or constructor. Handle memory management manually.
        }

        return input;
    }

    void MLibraryV2::MImage::Save(BinaryWriter *writer)
    {
        writer->Write(Width);
        writer->Write(Height);
        writer->Write(X);
        writer->Write(Y);
        writer->Write(ShadowX);
        writer->Write(ShadowY);
        writer->Write(HasMask ? static_cast<unsigned char>(Shadow | 0x80) : static_cast<unsigned char>(Shadow));
        writer->Write(FBytes.size());
        writer->Write(FBytes);
        if (HasMask)
        {
            writer->Write(MaskWidth);
            writer->Write(MaskHeight);
            writer->Write(MaskX);
            writer->Write(MaskY);
            writer->Write(MaskFBytes.size());
            writer->Write(MaskFBytes);
        }
    }

    std::vector<unsigned char> MLibraryV2::MImage::Compress(std::vector<unsigned char> &raw)
    {
//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (MemoryStream memory = new MemoryStream())
        {
            MemoryStream memory = MemoryStream();
//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (GZipStream gzip = new GZipStream(memory, CompressionMode.Compress, true))
            {
                GZipStream gzip = GZipStream(memory, CompressionMode::Compress, true);
                gzip.Write(raw, 0, raw.size());
            }
            return memory.ToArray();
        }
    }

    std::vector<unsigned char> MLibraryV2::MImage::Decompress(std::vector<unsigned char> &gzip)
    {
        // Create a GZIP stream with decompression mode.
        // ... Then create a buffer and write into while reading from the GZIP stream.
//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
        {
            MemoryStream tempVar(gzip);
            GZipStream stream = GZipStream(&tempVar, CompressionMode::Decompress);
            constexpr int size = 4096;
            std::vector<unsigned char> buffer(size);
//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (MemoryStream memory = new MemoryStream())
            {
                MemoryStream memory = MemoryStream();
                int count = 0;
                do
                {
                    count = stream.Read(buffer, 0, size);
                    if (count > 0)
                    {
                        memory.Write(buffer, 0, count);
                    }
                } while (count > 0);
                return memory.ToArray();
            }
        }
    }

    void MLibraryV2::MImage::CreatePreview()
    {
        if (Image == nullptr)
        {
            Preview = new Bitmap(1, 1);
            return;
        }

        Preview = new Bitmap(64, 64);

//C# TO C++ CONVERTER NOTE: The following 'using' block is replaced by its C++ equivalent:
//ORIGINAL LINE: using (Graphics g = Graphics.FromImage(Preview))
        {
            Graphics g = Graphics::FromImage(Preview);
            g.InterpolationMode = InterpolationMode::Low; //HighQualityBicubic
            g.Clear(Color::Transparent);
            int w = std::min(static_cast<int>(Width), 64);
            int h = std::min(static_cast<int>(Height), 64);
            Rectangle tempVar((64 - w) / 2, (64 - h) / 2, w, h);
            Rectangle tempVar2(0, 0, Width, Height);
            g.DrawImage(Image, &tempVar, &tempVar2, GraphicsUnit::Pixel);

            g.Save();
        }
    }
}
